#include "graphics.h"
#include "helper.h"

extern HWND hWnd;

Graphics::Graphics() :
	device(0), swapChain(0), renderTargetView(0), depthStencil(0),
			depthStencilView(0), solidState(0), wireframed(0), coloured(0),
			mesh(0), skybox(0), camera(0){
}

Graphics::~Graphics() {
}

bool Graphics::InitialiseGraphics() {
	HRESULT hr = S_OK;

	RECT rect;
	GetClientRect(hWnd, &rect);

	// window dimensions
	UINT width = rect.right - rect.left;
	UINT height = rect.bottom - rect.top;

	// swapchain description
	DXGI_SWAP_CHAIN_DESC sd;
	ZeroMemory(&sd, sizeof(sd));
	sd.BufferCount = 1;
	sd.BufferDesc.Width = width;
	sd.BufferDesc.Height = height;
	sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.OutputWindow = hWnd;
	sd.SampleDesc.Count = 1;
	sd.SampleDesc.Quality = 0;
	sd.Windowed = TRUE;

	// create swapchain and device
	hr = D3D10CreateDeviceAndSwapChain(NULL, // adapter
			D3D10_DRIVER_TYPE_HARDWARE, NULL, // software module
			NULL, // flags
			D3D10_SDK_VERSION, &sd, &swapChain, &device);

	if (FAILED(hr))
		return Error(L"Could not create device and swapchain");

	// get the back buffer (buffer at 0)
	ID3D10Texture2D *buffer;
	hr = swapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID *) &buffer);
	if (FAILED(hr))
		return Error(L"Could not acquire the back buffer");

	// create render target view the back buffer
	hr = device->CreateRenderTargetView(buffer,
			NULL /* render target description*/, &renderTargetView);
	if (FAILED(hr))
		return Error(L"Could not create render target");

	// get rid of buffer, no longer needed
	Release(buffer);

	// depth buffer description
	D3D10_TEXTURE2D_DESC depthBufferDesc;
	ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));

	depthBufferDesc.Width = width;
	depthBufferDesc.Height = height;
	depthBufferDesc.MipLevels = 1;
	depthBufferDesc.ArraySize = 1;
	depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthBufferDesc.SampleDesc.Count = 1;
	depthBufferDesc.SampleDesc.Quality = 0;
	depthBufferDesc.Usage = D3D10_USAGE_DEFAULT;
	depthBufferDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
	depthBufferDesc.CPUAccessFlags = 0;
	depthBufferDesc.MiscFlags = 0;

	// create depth stencil texture
	hr = device->CreateTexture2D(&depthBufferDesc, NULL, &depthStencil);
	if (FAILED(hr))
		return Error(L"Could not create depth stencil texture");

	// depth stencil view description
	D3D10_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
	ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));
	depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthStencilViewDesc.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
	depthStencilViewDesc.Texture2D.MipSlice = 0;

	hr = device->CreateDepthStencilView(depthStencil, &depthStencilViewDesc,
			&depthStencilView);
	if (FAILED(hr))
		return Error(L"Could not create depth stencil view");

	device->OMSetRenderTargets(1, &renderTargetView, depthStencilView);

	// rasterizer states
	D3D10_RASTERIZER_DESC rasterDesc;
	rasterDesc.AntialiasedLineEnable = true;
	rasterDesc.CullMode = D3D10_CULL_NONE;
	rasterDesc.DepthBias = 0;
	rasterDesc.DepthBiasClamp = 0.0f;
	rasterDesc.DepthClipEnable = true;
	rasterDesc.FillMode = D3D10_FILL_SOLID;
	rasterDesc.FrontCounterClockwise = false;
	rasterDesc.MultisampleEnable = false;
	rasterDesc.ScissorEnable = false;
	rasterDesc.SlopeScaledDepthBias = 0.0f;

	// create solid rasterizer state
	hr = device->CreateRasterizerState(&rasterDesc, &solidState);
	if (FAILED(hr)) {
		return Error(L"Could not create solid rasterizer state.");
	}

	// TODO add wireframe mode
	device->RSSetState(solidState);

	camera = new Camera(D3DXVECTOR3(0.0f, 0.0f, -15.0f));
	if (NULL == camera)
		return false;

	skybox = new Skybox;
	if (NULL == skybox)
		return false;

	if (!skybox->Initialise(device))
		return false;

	/*coloured = new Coloured;
	if (coloured == NULL)
		return false;

	if (!coloured->InitialiseModels(device))
		return false;*/

	/*mesh = new Mesh;
	if (mesh == NULL)
		return false;

	if (!mesh->InitialiseModels(device))
		return false;
		*/
	// set up the view port
	D3D10_VIEWPORT viewport;
	viewport.Width = width;
	viewport.Height = height;
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	viewport.TopLeftX = 0;
	viewport.TopLeftY = 0;
	device->RSSetViewports(1, &viewport);

	// set up perspective
	float fov = (float) D3DX_PI * 0.25f;
	float aspect = (float) width / (float) height;

	D3DXMatrixPerspectiveFovLH(&Projection, fov, aspect, 0.1f, 1000.0f);

	return true;
}

void Graphics::Cleanup() {
	if (device != NULL)
		device->ClearState();

	if (coloured != NULL) {
		coloured->Cleanup();
		coloured = 0;
	}

	if (mesh != NULL) {
		mesh->Cleanup();
		mesh = 0;
	}

	if (skybox != NULL)
	{
		skybox->Cleanup();
		skybox = 0;
	}

	Release(solidState);
	Release(wireframed);
	Release(depthStencil);
	Release(depthStencilView);
	Release(renderTargetView);
	Release(swapChain);
	Release(device);
}

void Graphics::Render() {
	// handle input
	if (GetAsyncKeyState(VK_ESCAPE) & 0x8000)
		PostMessage(hWnd, WM_DESTROY, 0, 0);

	if (GetAsyncKeyState(VK_RIGHT) & 0x8000)
		camera->MoveRight(0.4f);

	if (GetAsyncKeyState(VK_LEFT) & 0x8000)
		camera->MoveRight(-0.4f);

	if (GetAsyncKeyState(VK_UP) & 0x8000)
		camera->MoveForward(0.4f);

	if (GetAsyncKeyState(VK_DOWN) & 0x8000)
		camera->MoveForward(-0.4f);
	// update timer
	// update camera

	D3DXMATRIX World;
	D3DXMatrixIdentity(&World);

	camera->GetViewMatrix(&View);
	// begin scene
	device->ClearRenderTargetView(renderTargetView, background);
	device->ClearDepthStencilView(depthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0u);

	//mesh->SetShaderVars(World, View, Projection);
	//mesh->Render(device);

	D3DXMatrixIdentity(&World);
	//coloured->SetShaderVars(World, View, Projection);
	//coloured->Render(device);
	// end scene

	D3DXMatrixIdentity(&World);
	skybox->SetShaderVars(World, View, Projection);
	skybox->Render(device);

	swapChain->Present(0, 0);
}

